/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.loader;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.jianggujin.modulelink.JAction;
import com.jianggujin.modulelink.JModule;
import com.jianggujin.modulelink.config.JModuleConfig;
import com.jianggujin.modulelink.exception.JInjectException;
import com.jianggujin.modulelink.support.JActionInject;
import com.jianggujin.modulelink.support.JActionInject.JActionArrayInject;
import com.jianggujin.modulelink.support.JActionInject.JActionCollectionInject;
import com.jianggujin.modulelink.support.JActionInject.JActionMapInject;
import com.jianggujin.modulelink.support.JActionInject.JActionSingleInject;
import com.jianggujin.modulelink.support.JActionProxyHandler;
import com.jianggujin.modulelink.support.JBeanProvider;
import com.jianggujin.modulelink.support.annotation.JInject;
import com.jianggujin.modulelink.support.plugin.JInterceptor;
import com.jianggujin.modulelink.support.plugin.JPlugin;
import com.jianggujin.modulelink.util.JModuleUtils;
import com.jianggujin.modulelink.util.JStringUtils;

/**
 * 模块加载器解析器抽象实现
 * 
 * @author jianggujin
 *
 */
public abstract class JAbstractModuleLoaderSolver<M extends JModule> implements JModuleLoaderSolver {
    /**
     * 安装拦截器
     * 
     * @param target
     * @param interceptors
     * @param module
     * @param injects
     * @return
     * @throws Exception
     */
    protected Object installAll(Object target, Collection<JInterceptor> interceptors, JModule module,
            List<JActionInject> injects) throws Exception {
        this.inject(target, module, injects);
        if (interceptors != null) {
            for (JInterceptor interceptor : interceptors) {
                target = interceptor.install(target);
            }
        }
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), JPlugin.getAllInterfaces(target.getClass()),
                new JActionProxyHandler(module, (JAction) target));
    }

    private List<JActionInject> inject(Object target, JModule module, List<JActionInject> injects) throws Exception {
        Class<?> type = target.getClass();
        while (!Object.class.equals(type) && type != null) {
            for (Field field : type.getDeclaredFields()) {
                int modifier = field.getModifiers();
                if (Modifier.isFinal(modifier) || Modifier.isStatic(modifier)) {
                    continue;
                }
                JInject inject = field.getAnnotation(JInject.class);
                if (inject == null) {
                    continue;
                }
                Class<?> fieldType = field.getType();
                String name = inject.value();
                // 存在指定Bean名称，按照名称注入
                if (!JStringUtils.isEmpty(name)) {
                    this.injectByName(field, target, module, inject, injects);
                    continue;
                }

                // 按照类型注入
                if (JModuleConfig.class.isAssignableFrom(fieldType)) {
                    JModuleUtils.inject(field, target, module.getModuleConfig());
                } else if (JAction.class.isAssignableFrom(fieldType)) {
                    injects.add(new JActionSingleInject(target, field, inject));
                } else if (fieldType.isArray()) {
                    this.injectArray(field, target, module, inject, injects);
                } else if (Collection.class.isAssignableFrom(fieldType)) {
                    this.injectCollection(field, target, module, inject, injects);
                } else if (Map.class.isAssignableFrom(fieldType)) {
                    this.injectMap(field, target, module, inject, injects);
                } else {
                    this.injectByType(field, target, module, inject);
                }

            }
            type = type.getSuperclass();
        }
        return null;
    }

    /**
     * 通过Bean名称注入
     * 
     * @param field
     * @param target
     * @param module
     * @param inject
     * @param injects
     * @throws Exception
     */
    private void injectByName(Field field, Object target, JModule module, JInject inject, List<JActionInject> injects)
            throws Exception {
        JBeanProvider provider = module.getBeanProvider();
        Object value = provider == null ? null : provider.optBean(inject.value());
        // 未找到Bean
        if (value == null) {
            // Action的特殊处理，name为指定Action名称
            if (JAction.class.isAssignableFrom(field.getType())) {
                injects.add(new JActionSingleInject(target, field, inject));
                return;
            }
            if (inject.required()) {
                throw new JInjectException("required [" + field.getName() + "] value must not be null");
            }
        }
        JModuleUtils.inject(field, target, value);
    }

    /**
     * 注入数组类型
     * 
     * @param field
     * @param target
     * @param module
     * @param inject
     * @param injects
     * @throws Exception
     */
    private void injectArray(Field field, Object target, JModule module, JInject inject, List<JActionInject> injects)
            throws Exception {
        Class<?> fieldType = field.getType();
        Class<?> componentType = fieldType.getComponentType();
        JBeanProvider provider = module.getBeanProvider();
        Collection<?> values = provider == null ? null : provider.getBeansOfType(componentType).values();
        Object value = null;
        if (values == null || values.isEmpty()) {
            if (JAction.class.isAssignableFrom(componentType)) {
                injects.add(new JActionArrayInject(target, field));
                return;
            }
            if (inject.required()) {
                throw new JInjectException("required [" + field.getName() + "] value must not be null");
            }
        } else {
            value = Array.newInstance(componentType, values.size());
            int i = 0;
            for (Object object : values) {
                Array.set(value, i++, object);
            }
        }
        JModuleUtils.inject(field, target, value);
    }

    /**
     * 注入集合类型
     * 
     * @param field
     * @param target
     * @param module
     * @param inject
     * @param injects
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private void injectCollection(Field field, Object target, JModule module, JInject inject,
            List<JActionInject> injects) throws Exception {
        Class<?> fieldType = field.getType();
        Collection<Object> value = null;
        if (fieldType.isInterface()) {
            if (List.class.isAssignableFrom(fieldType)) {
                value = new ArrayList<Object>();
            } else if (Set.class.isAssignableFrom(fieldType)) {
                value = new HashSet<Object>();
            }
        } else {
            value = (Collection<Object>) fieldType.newInstance();
        }

        Type[] componentTypes = getComponentType(field);
        if (componentTypes == null || componentTypes.length == 0) {
            throw new JInjectException("required [" + field.getName() + "] value type invalid");
        }
        if (!(componentTypes[0] instanceof Class<?>)) {
            throw new JInjectException("required [" + field.getName() + "] value type invalid");
        }
        Class<?> componentType = (Class<?>) componentTypes[0];

        JBeanProvider provider = module.getBeanProvider();
        Collection<?> values = provider == null ? null : provider.getBeansOfType(componentType).values();
        if (values == null) {
            if (JAction.class.isAssignableFrom(componentType)) {
                injects.add(new JActionCollectionInject(target, field, value));
                return;
            }
            if (inject.required()) {
                throw new JInjectException("required [" + field.getName() + "] value must not be null");
            }
        } else {
            value.addAll(values);
        }
        JModuleUtils.inject(field, target, value);
    }

    /**
     * 注入Map类型
     * 
     * @param field
     * @param target
     * @param module
     * @param inject
     * @param injects
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private void injectMap(Field field, Object target, JModule module, JInject inject, List<JActionInject> injects)
            throws Exception {
        Class<?> fieldType = field.getType();
        Map<String, Object> value = null;
        if (fieldType.isInterface()) {
            value = new HashMap<String, Object>();
        } else {
            value = (Map<String, Object>) fieldType.newInstance();
        }

        Type[] componentTypes = getComponentType(field);
        if (componentTypes == null || componentTypes.length == 0) {
            throw new JInjectException("required [" + field.getName() + "] value type invalid");
        }
        if (!String.class.equals(componentTypes[0])) {
            throw new JInjectException("required [" + field.getName() + "] value type invalid");
        }
        if (!(componentTypes[1] instanceof Class<?>)) {
            throw new JInjectException("required [" + field.getName() + "] value type invalid");
        }
        Class<?> componentType = (Class<?>) componentTypes[1];

        JBeanProvider provider = module.getBeanProvider();
        Map<String, ?> values = provider == null ? null : provider.getBeansOfType(componentType);
        if (values == null || values.isEmpty()) {
            if (JAction.class.isAssignableFrom(componentType)) {
                injects.add(new JActionMapInject(target, field, value));
                return;
            }
            if (inject.required()) {
                throw new JInjectException("required [" + field.getName() + "] value must not be null");
            }
        } else {
            value.putAll(values);
        }
        JModuleUtils.inject(field, target, value);
    }

    private Type[] getComponentType(Field field) {
        Type type = field.getGenericType();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return parameterizedType.getActualTypeArguments();
        }
        return null;
    }

    /**
     * 根据类型注入
     * 
     * @param field
     * @param target
     * @param module
     * @param inject
     * @throws Exception
     */
    private void injectByType(Field field, Object target, JModule module, JInject inject) throws Exception {
        JBeanProvider provider = module.getBeanProvider();
        Object value = provider == null ? null : provider.optBean(field.getType());
        // 未找到Bean
        if (value == null) {
            if (inject.required()) {
                throw new JInjectException("required [" + field.getName() + "] value must not be null");
            }
        }
        JModuleUtils.inject(field, target, value);
    }
}